using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;

namespace cssp
{
	class Program
	{
		class Options
		{
			public bool Recurse;
			public string[] Filenames;
			public string OutputPath;
			public bool OutputToConsole;
			public LogLevel LogLevel;

			public Options()
			{
				Recurse = false;
				Filenames = new string[] { };
				OutputPath = Directory.GetCurrentDirectory();
				OutputToConsole = false;
				LogLevel = LogLevel.Info;
			}
		}

		static Options mOptions = null;
		static CssxProcessor mProcessor = null;

		static int Main(string[] args)
		{
			int result = 0;
			Log.Logger = new ConsoleLogger();
			try
			{
				/*
				 * Parse command-line arguments
				 */

				if (args.Length == 0 || args[0] == "/?")
				{
					PrintHelp();
					return 0;
				}

				mOptions = new Options();
				if (!ParseArguments(args))
					return 1;

				Log.Logger.LogLevel = mOptions.LogLevel;

				/*
				 * Process files
				 */

				mProcessor = new CssxProcessor();
				CssWriter writer = new CssWriter();
				writer.GetStream = GetStream;

				// If output path doesn't exist, create it.
				if (!Directory.Exists(mOptions.OutputPath))
				{
					Log.WriteLine(LogLevel.Debug, "Creating output path '" + mOptions.OutputPath + "'...");
					Directory.CreateDirectory(mOptions.OutputPath);
				}

				foreach (string filename in mOptions.Filenames)
				{
					Unit unit = mProcessor.Process(filename);
					if (unit.HasErrors)
						result = 1;
					else
						writer.Write(unit);
				}

				return result;
			}
			catch (Exception ex)
			{
#if DEBUG
				Log.WriteLine(LogLevel.Error, ex.ToString());
#else
				Log.WriteLine(LogLevel.Error, ex.Message);
#endif
				return 1;
			}
		}

		static Stream GetStream(string filename)
		{
			Log.WriteLine(LogLevel.Info, "Writing " + Path.GetFileName(filename) + ":");
			if (mOptions.OutputToConsole)
				return Console.OpenStandardOutput();
			else
				return File.Open(Path.Combine(mOptions.OutputPath, filename), FileMode.Create);
		}

		/// <summary>
		/// Read command-line arguments, expand wildcards, and populate mOptions. 
		/// </summary>
		/// <param name="args">
		/// array of arguments
		/// </param>
		/// <returns>
		/// A <see cref="System.Boolean"/>
		/// </returns>
		static bool ParseArguments(string[] args)
		{
			List<string> filenames = new List<string>();
			string currArg = "";
			try
			{
				foreach (string arg in args)
				{
					currArg = arg;
					if (arg == "/r" || arg == "/recurse")
					{
						mOptions.Recurse = true;
					}
					else if (arg == "/c" || arg == "/console")
					{
						mOptions.OutputToConsole = true;
					}
					else if (arg.StartsWith("/i:") || arg.StartsWith("/info:"))
					{
						int level = 0, start = arg.IndexOf(":") + 1;
						if (Int32.TryParse(arg.Substring(start), out level))
						{
							if (level >= 1 && level < (int)LogLevel.Debug)
							{
								mOptions.LogLevel = (LogLevel)(level + 1);
								continue;
							}
						}

						Console.Error.WriteLine("/info: expecting number (1-4).");
						return false;
					}
					else if (arg.StartsWith("/o:") || arg.StartsWith("/out:"))
					{
						int start = arg.IndexOf(':') + 1;
						if (start > 0)
						{
							string path = Path.GetFullPath(arg.Substring(start));
							mOptions.OutputPath = path;
						}
						else
							Console.Error.WriteLine("Output path is empty; current path will be used, instead.");
					}
					else
					{
						// On Linux, the shell may automatically expand wildcards, and add the file names
						// to argument list. In that case we simply pick them up one by one, and append them
						// to the filenames list.
						// Otherwise, we need to resolve the wildcard ourselves...
						if (arg.IndexOf('*') > -1 || arg.IndexOf('?') > -1)
						{
							string path = Path.GetDirectoryName(arg);
							if (path == String.Empty)
								path = Directory.GetCurrentDirectory();
							string pattern = Path.GetFileName(arg);
							GetFilenames(path, pattern, filenames, mOptions.Recurse);
						}
						else
							if ((File.GetAttributes(arg) & FileAttributes.Directory) == 0)
								filenames.Add(Path.GetFullPath(arg));
					}
				}
			}
			catch (Exception ex)
			{
				throw new Exception("Error parsing argument: " + currArg, ex);
			}

			mOptions.Filenames = filenames.ToArray();

			// verify that required options are present, and all options make sense
			if (mOptions.Filenames.Length == 0)
			{
				Console.Error.WriteLine("No files to process.");
				return false;
			}

			return true;
		}

		static void GetFilenames(string path, string pattern, List<string> filenames, bool recurse)
		{
			string[] files = Directory.GetFiles(path, pattern);
			foreach (string f in files)
			{
				filenames.Add(Path.GetFullPath(f));
			}

			if (recurse)
			{
				string[] dirs = Directory.GetDirectories(path);
				foreach (string d in dirs)
				{
					GetFilenames(d, pattern, filenames, true);
				}
			}
		}

		static void PrintHelp()
		{
			Assembly assembly = Assembly.GetExecutingAssembly();
			Stream s = assembly.GetManifestResourceStream("cssp.help.txt");
			string text = new StreamReader(s).ReadToEnd();
			text = text.Replace("{ver}", assembly.GetName().Version.ToString());
			Console.WriteLine(text);
		}
	}
}
